package jacky.lanlan.song.extension.struts.util;

import java.util.concurrent.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.apache.log4j.Logger;

/**
 * 高效并发缓存，线程安全。低层实现依赖于 {@link java.util.concurrent.ConcurrentHashMap}。
 * <p>
 * 通过指定的工厂，这个类会通过工厂创建对象并缓存(如果有必要)。
 * <p>
 * 可以接受多线程的并发访问，并能够维持吞吐量。
 * <p>
 * <i>参考《Java Concurrent in Practise》中的示例代码实现。</i>
 * @param <K> 缓存的键类型
 * @param <V> 缓存的值类型
 * @param <P> 工厂创建对象需要的参数类型
 * @author Jacky.Song
 */
public class ConcurrentCache<K, V, P> {
	private static final Logger logger=Logger.getLogger(ConcurrentCache.class);
	
	private final ConcurrentMap<K, Future<V>> cache = new ConcurrentHashMap<K, Future<V>>();

	private final ReturnableHandler<P, V> factory;

	private final ReadWriteLock lock=new ReentrantReadWriteLock();
	
	public ConcurrentCache(ReturnableHandler<P, V> factory) {
		this.factory = factory;
	}

	/**
	 * 清空缓存。
	 */
	public void clear() {
		cache.clear();
	}
	
	/**
	 * 缓存大小。
	 */
	public int size() {
		return cache.size();
	}
	
	/**
	 * 检查某个key是否有缓存，如果没有，就通过工厂创建并缓存，然后返回这个缓存值。
	 * @param key 键
	 * @param param 创建缓存值需要的参数
	 * @return 已缓存的值
	 * @throws InterruptedException
	 */
	public V cacheIfAbsent(final K key,final P param) throws InterruptedException {
		/* 
		 * 这个缓存的实现很特别，没有用同步或锁，确一样达到了线程安全。
		 * 
		 * 特别的地方就是它没有直接缓存目标对象，而是缓存了一个Future。
		 * 
		 * Future具有异步特性，它可以把运算延后，不会在运算的时候阻塞线程，这样，
		 * 就可以把 “检测某个值是否被缓存” 和 “如果没有缓存就创建它并缓存” 分开，
		 * 这样，即使某个创建很耗时，也不会妨碍其他已经被创建好的值的检测和获取。 
		 * 
		 * 比起加锁和同步的方式，这种机制可以达到很高的吞吐量，甚至和不同步状态下
		 * 的吞吐量相当，在实现线程安全的同时，又不影响性能。
		 */
		while (true) {
			Future<V> f = cache.get(key);//首先，一个线程检查，key有没有缓存
			if (f == null) {//如果没有缓存
				//创建一个FutureTask，这是一个可以延迟执行的类
				FutureTask<V> ft = new FutureTask<V>(new Callable<V>() {
					public V call() throws InterruptedException {
						return factory.doWith(param);
					}
				});
				
				f = cache.putIfAbsent(key, ft);//检查再缓存，注意是缓存Future，所以这里速度很快，不阻塞
				//putIfAbsent是一个原子方法，可以保证不发生线程争用，所以也就不会有缓存两个值的风险，换句话
				//说，下面的if只可能有一个线程进入
				
				if (f == null) {//null表示Future刚刚被缓存，但是任务还没有执行
					f = ft;
					ft.run();//执行任务，创建好真正的需要缓存的值。这里可能很耗时，但是只有一个线程会阻塞
				}
			}
			try {
				return f.get();//其他线程在这里阻塞，直到运行FutureTask的线程完成任务，然后它得到结果，如果任务已经完成，则立即返回结果
			}
			catch (CancellationException e) {
				cache.remove(key, f);//如果取消了操作，就一定要把任务从缓存清除，否则会污染缓存，因为这个任务已失效
				//这里往下走，就是while循环，重新执行缓存操作，直到这个key被成功缓存为止
			}
			catch (ExecutionException e) {
				throw new RuntimeException(e.getCause());
			}
		}
	}

	/**
	 * 刷新缓存数据。
	 * @param key
	 * @param param
	 */
	public void refresh(K key,P param) throws InterruptedException {
		lock.writeLock().lock();
		try {
			cache.remove(key);
			this.cacheIfAbsent(key, param);
		}
		finally {
			lock.writeLock().unlock();
		}
	}
	
	/**
	 * 删除缓存值。
	 * @param key 目标值对应的键
	 * @return 被删除的值，或null如果没有与key关联的值
	 */
	public V remove(K key) {
		lock.writeLock().lock();
		try {
			Future<V> f=cache.remove(key);
			return f==null ? null : f.get();
		}
		catch (Exception e) {
			logger.error("删除缓存值 [key:"+key+"] 时出错",e);
			throw new RuntimeException("删除缓存值 [key:"+key+"] 时出错",e);
		}
		finally {
			lock.writeLock().unlock();
		}
	}
	
	/**
	 * 得到缓存值。
	 * @param key 该值关联的键
	 * @return key所关联的值，或null如果key没有关联任何值
	 */
	public V get(K key) {
		lock.readLock().lock();
		try {
			Future<V> f=cache.get(key);
			return f==null ? null : f.get();
		}
		catch (Exception e) {
			logger.error("得到缓存值 [key:"+key+"] 时出错",e);
			throw new RuntimeException("得到缓存值 [key:"+key+"] 时出错",e);
		}
		finally {
			lock.readLock().unlock();
		}
	}
	
	/**
	 * 判断某个键是否存在。
	 * @param key 要检查的键
	 * @return true如果存在
	 */
	public boolean containsKey(K key) {
		return this.cache.containsKey(key);
	}
	
}